package com.example.weixinredpackokhttp.service;

import cn.hutool.crypto.digest.MD5;
import cn.hutool.log.StaticLog;
import com.example.weixinredpackokhttp.model.RedPackReqDto;
import com.example.weixinredpackokhttp.model.RedPackRespDto;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import okhttp3.*;
import okio.Buffer;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.*;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 红包发送类
 * @author shadow
 */
@Component
public class RedPackSender {

    @Value("${weixin.appid}")
    private String appId;
    @Value("${weixin.pay_mach_id}")
    private String mchId;
    @Value("${service.client_ip}")
    private String clientIp;
    @Value("${weixin.cert_path}")
    private String certPath;
    @Value("${weixin.pay_key}")
    private String appKey;

    // 初始化okhttp
    private static OkHttpClient okHttpClient;

    @PostConstruct
    public void init() throws KeyStoreException, IOException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, CertificateException {
        SSLSocketFactory socketFactory = getSSL().getSocketFactory();
        okHttpClient = new OkHttpClient.Builder()
                .sslSocketFactory(socketFactory, trustManager())
                .addInterceptor(new LoggingInterceptor())
                .build();
    }

    /**
     * 获取信任证书管理
     * @return
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws KeyStoreException
     * @throws IOException
     */
    private X509TrustManager trustManager() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(getKeyStore());
        return (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
    }

    /**
     * 获取安全ssl
     * @return
     * @throws KeyStoreException
     * @throws IOException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableKeyException
     * @throws KeyManagementException
     */
    private SSLContext getSSL() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
        // 创建 keyManager
        final KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmFactory.init(getKeyStore(), mchId.toCharArray());
        final KeyManager[] kms = kmFactory.getKeyManagers();

        // 创建 SSLContext
        SSLContext tls = SSLContext.getInstance("TLS");
        tls.init(kms, null, null);
        return tls;
    }

    /**
     * 加载证书
     * @return
     * @throws KeyStoreException
     * @throws IOException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     */
    private KeyStore getKeyStore() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        //证书位置自己定义
        try (FileInputStream is = new FileInputStream(certPath)) {
            keyStore.load(is, mchId.toCharArray());
        }
        return keyStore;
    }

    /**
     * OKHTTP日志拦截器
     */
    private static class LoggingInterceptor implements Interceptor {
        @NotNull
        @Override
        public Response intercept(Chain chain) throws IOException {
            long t1 = System.nanoTime();
            Request request = chain.request();
            StaticLog.info(String.format("Sending request %s on %s%n%s",
                    request.url(), chain.connection(), request.headers()));
            Buffer requestBuffer = new Buffer();
            assert request.body() != null;
            request.body().writeTo(requestBuffer);
            StaticLog.info("request body:{}", requestBuffer.readUtf8());

            Response response = chain.proceed(request);

            long t2 = System.nanoTime();
            StaticLog.info(String.format("Received response for %s in %.1fms%n%s",
                    request.url(), (t2 - t1) / 1e6d, response.headers()));

            assert response.body() != null;
            MediaType contentType = response.body().contentType();
            String content = response.body().string();
            StaticLog.info("response body:{}", content);
            ResponseBody wrappedBody = ResponseBody.create(contentType, content);

            return response.newBuilder().body(wrappedBody).build();
        }
    }


    /**
     * 使用OKHTTP异步发红包
     *
     * @param openId 发送对象的openId
     * @param money  发送的金额
     * @return
     */
    public void sendRedPackByOk(String openId, String money) {
        RequestBody body = RequestBody.create(MediaType.parse("text/xml;charset=UTF-8"), getParams(openId, money));
        Request request = new Request.Builder()
                .url("https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack")
                .post(body)
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                StaticLog.error("发送红包调用微信接口失败", e);
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                StaticLog.info("发送红包调用微信接口成功");
                assert response.body() != null;
                String respXml = response.body().string();
                XStream respXs = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
                respXs.processAnnotations(RedPackRespDto.class);
                RedPackRespDto resp = (RedPackRespDto) respXs.fromXML(respXml);
                StaticLog.info("发送红包结果自己封装保存:{}", resp);
            }
        });
    }

    /**
     * 封装请求参数
     * @param openId 发送对象的openId
     * @param money  发送的金额
     * @return
     */
    private String getParams(String openId, String money) {
        RedPackReqDto req = new RedPackReqDto();
        req.setNonce_str(UUID.randomUUID().toString().replace("-", "").toUpperCase());
        req.setMch_billno(getMchBillNo(mchId));
        req.setMch_id(mchId);
        req.setWxappid(appId);
        req.setClient_ip(clientIp);
        req.setSend_name("shadow");
        req.setRe_openid(openId);
        req.setTotal_amount(new BigDecimal(money).multiply(BigDecimal.valueOf(100)).intValue());
        req.setTotal_num(1);
        req.setWishing("大吉大利");
        req.setAct_name("今晚吃鸡");
        req.setRemark("恭喜发财");
        req.setSign(getSign(req));

        // dto to xml
        XStream reqXs = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
        reqXs.processAnnotations(RedPackReqDto.class);
        return reqXs.toXML(req);
    }


    /**
     * 获取商户订单号 组成：mch_id+yyyymmdd+10位一天内不能重复的数字。
     *
     * @param mchId
     * @return
     */
    private String getMchBillNo(String mchId) {
        // 获取yyyymmdd
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String formatDate = sdf.format(date);
        // 获取10位一天内不能重复的数字
        long time = date.getTime();
        String tenNo = new StringBuilder(Long.toString(time)).reverse()
                .substring(0, 10);
        return mchId + formatDate + tenNo;
    }


    /**
     * 获取签名
     *
     * @param req
     * @return
     */
    private String getSign(RedPackReqDto req) {

        TreeMap<String, String> sortMap = new TreeMap<>();
        sortMap.put("nonce_str", req.getNonce_str());
        sortMap.put("mch_billno", req.getMch_billno());
        sortMap.put("mch_id", req.getMch_id());
        sortMap.put("wxappid", req.getWxappid());
        sortMap.put("send_name", req.getSend_name());
        sortMap.put("re_openid", req.getRe_openid());
        sortMap.put("total_amount", req.getTotal_amount().toString());
        sortMap.put("total_num", req.getTotal_num().toString());
        sortMap.put("wishing", req.getWishing());
        sortMap.put("client_ip", req.getClient_ip());
        sortMap.put("act_name", req.getAct_name());
        sortMap.put("remark", req.getRemark());

        StringBuilder stringTemp = new StringBuilder();
        Iterator<Map.Entry<String, String>> it = sortMap.entrySet().iterator();
        boolean first = true;
        while (it.hasNext()) {
            Map.Entry<String, String> e = it.next();
            String key = e.getKey();
            String val = e.getValue();

            // sign字段不参与签名, 值为空的字段不参与签名
            // 其余字段,用key1=val1&key2=val2格式拼接起来
            if (!"sign".equals(key) && val != null && val.length() != 0) {
                if (first) {
                    first = false;
                } else {
                    stringTemp.append("&");
                }
                stringTemp.append(key).append("=").append(val);
            }
        }
        stringTemp.append("&key=").append(appKey);
        StaticLog.info("待签名:" + stringTemp);
        return MD5.create().digestHex(stringTemp.toString()).toUpperCase();
    }


}
